﻿using Microsoft.AspNetCore.Mvc;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Processing;

using System.Diagnostics;

namespace anydata.DataStorage
{
    public class BucketManager
    {
        private string _rootPath;
        private string _ffmpegPath;
        public long _chunkSize { get; set; }
        public BucketManager(long chunkSize = 1024 * 1024)
        {
            _chunkSize = chunkSize;
            _rootPath = Config.Bucket;
            _ffmpegPath = Config.FfmpegPath;
        }

        public async Task<IActionResult> ExecuteResult(TokenModel token, BucketItemData item)
        {
            var result = await Execute(token, item);
            if (result.success && item.operate == SupportOpreate.Download)
            {
                var fileInfo = result.data as FileInfo;
                if (fileInfo != null)
                {
                    return new FileStreamResult(fileInfo.OpenRead(), fileInfo.Extension.ContentType())
                    {
                        FileDownloadName = fileInfo.Name,
                    };
                }
            }
            return new JsonResult(result);
        }

        public async Task<HubFileResult> LoadFileRange(string key, long startIndex)
        {
            var result = new HubFileResult();
            result.startIndex = startIndex;
            result.statusCode = 404;
            result.end = true;
            result.errorMsg = "not found!";
            var exeRes = await Execute(new TokenModel(), new BucketItemData() { key = key, operate = SupportOpreate.Download });
            if (exeRes.success)
            {
                var fileInfo = exeRes.data as FileInfo;
                if (fileInfo != null && fileInfo != null && fileInfo.Exists)
                {
                    result.statusCode = 200;
                    result.errorMsg = "";
                    result.fileName = fileInfo.Name;
                    result.contentLength = fileInfo.Length;
                    result.contentType = fileInfo.Extension.ContentType(false);
                    result.modTime = fileInfo.LastWriteTime.ToString("yyyy-MM-dd HH:mm:ss");
                    try
                    {
                        using (var stream = fileInfo.OpenRead())
                        {
                            stream.Seek(result.startIndex, SeekOrigin.Begin);
                            result.endIndex = result.startIndex + _chunkSize;
                            result.end = result.endIndex >= fileInfo.Length;
                            if (result.end)
                            {
                                result.endIndex = fileInfo.Length;
                            }
                            var buffer = new byte[result.endIndex - result.startIndex];
                            stream.Read(buffer, 0, buffer.Length);
                            result.content = Convert.ToBase64String(buffer);
                            stream.Close();
                        }
                    }
                    catch (Exception ex)
                    {
                        result.end = true;
                        result.statusCode = 500;
                        result.errorMsg = ex.Message;
                    }
                }
            }
            return result;
        }

        public Task<OperateResult> Execute(TokenModel token, BucketItemData item)
        {
            var result = new OperateResult();
            try
            {
                var config = new BucketConfig(_rootPath, "", _chunkSize, _ffmpegPath);
                if (token.BelongId > 0)
                {
                    config = new BucketConfig(_rootPath, token.DbName, _chunkSize, _ffmpegPath);
                }
                result = OperateResult.Success(new FileSystemManager(item, config).Execute());
            }
            catch (Exception ex)
            {
                result = OperateResult.Faild(ex.Message);
            }
            return Task.FromResult(result);
        }

        public DiskInfoItem DiskInfo(TokenModel token)
        {
            try
            {
                var config = new BucketConfig(_rootPath, "", _chunkSize, _ffmpegPath);
                if (token.BelongId > 0)
                {
                    config = new BucketConfig(_rootPath, token.DbName, _chunkSize, _ffmpegPath);
                }
                return new FileSystemManager(new BucketItemData()
                {
                    key = "",
                    operate = SupportOpreate.Info,
                }, config).Execute() as DiskInfoItem;
            }
            catch
            {
                return new DiskInfoItem();
            }
        }
    }

    internal class FileSystemManager
    {
        private BucketConfig _config;
        private BucketItemData _item;
        private FileSystemService _service;
        public FileSystemManager(BucketItemData item, BucketConfig config)
        {
            _item = item;
            _config = config;
            _service = new FileSystemService(_config.user, item.targetId, config.ffmpegPath);
        }
        public object? Execute()
        {
            switch (_item.operate)
            {
                case SupportOpreate.Info:
                    return _service.DiskInfo(_item.KeyPath(_config.user));
                case SupportOpreate.List:
                    return _service.GetItems(_item.KeyPath(_config.user));
                case SupportOpreate.Download:
                    return _service.GetFile(_item.KeyPath(_config.user));
                case SupportOpreate.HslSplit:
                    return _service.HslSplit(_item.KeyPath(_config.user));
                case SupportOpreate.Create:
                    return _service.Create(_item.KeyPath(_config.user));
                case SupportOpreate.Delete:
                    return _service.Delete(_item.KeyPath(_config.user));
                case SupportOpreate.Rename:
                    return _service.Rename(_item.KeyPath(_config.user), _item.GetName());
                case SupportOpreate.Copy:
                    return _service.Copy(_item.KeyPath(_config.user), _item.GetDestination(_config.user));
                case SupportOpreate.Move:
                    return _service.Move(_item.KeyPath(_config.user), _item.GetDestination(_config.user));
                case SupportOpreate.AbortUpload:
                    return _service.AbortUpload(_config.temp, _item.GetData(_config.chunkSize));
                case SupportOpreate.Upload:
                    return _service.Upload(_item.KeyPath(_config.user), _config.temp, _item.GetData(_config.chunkSize));
                default:
                    return "操作不被支持";
            }
        }
    }

    internal class FileSystemService
    {
        private readonly DirectoryInfo baseDir;
        private readonly string _ffmpegPath;
        private readonly bool ffmpegEnabled;
        private readonly string _targetFlag = "";
        public FileSystemService(string originalPath, ulong targetId, string ffmpegPath)
        {
            _ffmpegPath = ffmpegPath;
            if (targetId > 10000) {
                _targetFlag = $"{targetId}".ToBase32() + "0";
            }
            baseDir = new DirectoryInfo(originalPath);
            ffmpegEnabled = !string.IsNullOrWhiteSpace(_ffmpegPath) && File.Exists(_ffmpegPath);
        }
        public FileSysItem Create(string path)
        {
            CreateDirectory(path);
            return createFileSysItem(new DirectoryInfo(path));
        }

        public FileSysItem Rename(string sourceName, string newName)
        {
            newName = ConfirmFile(newName);
            if (File.Exists(sourceName))
            {
                var destFileName = Path.Combine(new FileInfo(sourceName).Directory?.FullName ?? "", newName);
                File.Move(sourceName, destFileName);
                return createFileSysItem(new FileInfo(destFileName));
            }
            if (Directory.Exists(sourceName))
            {
                var destFileName = Path.Combine(new DirectoryInfo(sourceName).Parent?.FullName ?? "", newName);
                Directory.Move(sourceName, destFileName);
                return createFileSysItem(new DirectoryInfo(destFileName));
            }
            throw new Exception("重命名文件不存在！");
        }

        public FileInfo GetFile(string path)
        {
            if (File.Exists(path))
            {
                return new FileInfo(path);
            }
            throw new Exception("文件不存在！");
        }

        public bool HslSplit(string path)
        {
            if (!ffmpegEnabled)
            {
                throw new Exception("视频切片不支持！");
            }
            var fileInfo = new FileInfo(path);
            if (fileInfo.Exists)
            {
                if (fileInfo.Extension.ContentType().StartsWith("video"))
                {
                    var mp4File = new FileInfo(fileInfo.FullName.Replace(fileInfo.Extension, ".mp4"));
                    if (!Directory.Exists(Path.Combine(mp4File.FullName + ".slices")))
                    {
                        splitVideoToHsl(fileInfo);
                        return true;
                    }
                    throw new Exception("切片文件已经存在！");
                }
                throw new Exception("只支持视频文件！");
            }
            throw new Exception("文件不存在！");
        }

        public DiskInfoItem DiskInfo(string path)
        {
            var dir = new DirectoryInfo(path);
            var infoFile = new FileInfo(Path.Combine(path, "info.json"));
            if (infoFile.Exists)
            {
                var data = File.ReadAllText(infoFile.FullName).ToObject<DiskInfoItem>();
                if (data != null && DateTime.Now.Subtract(data.lastTime).TotalMinutes < 5)
                {
                    return data;
                }
            }
            var info = new DiskInfoItem();
            if (dir.Exists)
            {
                DirectoryInfo(dir, ref info);
                File.WriteAllText(infoFile.FullName, info.ToJson());
            }
            return info;
        }

        public void DirectoryInfo(DirectoryInfo dir, ref DiskInfoItem info)
        {
            var files = dir.GetFiles();
            info.fileCount += files.Length;
            info.size += files.Select(f => f.Length).Sum();
            var dirs = dir.GetDirectories();
            foreach (var item in dirs)
            {
                DirectoryInfo(item, ref info);
            }
        }

        public List<FileSysItem> GetItems(string path)
        {
            CreateDirectory(path);
            var dir = new DirectoryInfo(path);
            if (!dir.Exists)
            {
                throw new Exception("位置不存在!");
            }
            var items = new List<FileSysItem>();
            foreach (var item in dir.GetFiles())
            {
                if (!item.Name.EndsWith(".thumbnail"))
                {
                    items.Add(createFileSysItem(item));
                }
            }
            foreach (var item in dir.GetDirectories())
            {
                items.Add(createFileSysItem(item));
            }
            return items;
        }

        public void SymbolicLink(string sourceFileName, string symbolicLink)
        {
            if (File.Exists(symbolicLink))
            {
                File.Delete(symbolicLink);
            }
            File.CreateSymbolicLink(symbolicLink, sourceFileName);
        }

        public bool Move(string sourceName, string destDirName)
        {
            CreateDirectory(destDirName);
            if (!Directory.Exists(destDirName))
            {
                return false;
            }
            if (File.Exists(sourceName))
            {
                var destFileName = Path.Combine(destDirName, new FileInfo(sourceName).Name);
                File.Move(sourceName, destFileName, true);
                return true;
            }
            if (Directory.Exists(sourceName))
            {
                destDirName = Path.Combine(destDirName, new DirectoryInfo(sourceName).Name);
                Directory.Move(sourceName, destDirName);
                return true;
            }
            return false;
        }

        public bool Copy(string sourceName, string destDirName)
        {
            CreateDirectory(destDirName);
            if (!Directory.Exists(destDirName))
            {
                return false;
            }
            if (File.Exists(sourceName))
            {
                var destFileName = Path.Combine(destDirName, new FileInfo(sourceName).Name);
                File.Copy(sourceName, destFileName);
                return true;
            }
            if (Directory.Exists(sourceName))
            {
                destDirName = Path.Combine(destDirName, new DirectoryInfo(sourceName).Name);
                CopyDirectoryContentRecursive(new DirectoryInfo(sourceName), new DirectoryInfo(destDirName));
                return true;
            }
            return false;
        }

        public bool Delete(string path)
        {
            if (Directory.Exists(path))
            {
                Directory.Delete(path, true);
                return true;
            }
            if (File.Exists(path))
            {
                File.Delete(path);
                var thumbnail = path + ".thumbnail";
                if (File.Exists(thumbnail))
                {
                    File.Delete(thumbnail);
                }
                if (path.EndsWith(".smp4"))
                {
                    var slices = path.Substring(0, path.Length - 5) + ".slices";
                    if (Directory.Exists(slices))
                    {
                        Directory.Delete(slices, true);
                    }
                }
                return true;
            }
            return false;
        }

        public FileSysItem? Upload(string path, string tempPath, FileItemData data)
        {
            tempPath = Path.Combine(tempPath, data.uploadId + ".tmp");
            using (var stream = File.Open(tempPath, FileMode.Append, FileAccess.Write, FileShare.Read))
            {
                stream.Write(data.data, 0, data.data.Length);
            }
            if (data.lastChunk)
            {
                path = ConfirmFile(path);
                var newFile = new FileInfo(path);
                if (newFile.Directory != null)
                {
                    CreateDirectory(newFile.Directory.FullName);
                }
                File.Move(tempPath, path, true);
                return createFileSysItem(newFile, true);
            }
            return null;
        }

        public bool AbortUpload(string tempPath, FileItemData data)
        {
            tempPath = Path.Combine(tempPath, data.uploadId + ".tmp");
            if (File.Exists(tempPath))
            {
                File.Delete(tempPath);
            }
            return true;
        }
        private void CreateDirectory(string directory)
        {
            var info = new DirectoryInfo(directory);
            if (!info.Exists)
            {
                if (info.Parent != null)
                {
                    if (!info.Parent.Exists)
                    {
                        CreateDirectory(info.Parent.FullName);
                    }
                    Directory.CreateDirectory(info.FullName);
                }
            }
        }

        private string ConfirmFile(string destFileName, int index = 0)
        {
            FileInfo desInfo = new FileInfo(destFileName);
            if (index > 0)
            {
                var name = desInfo.FullName.Substring(0, desInfo.FullName.Length - desInfo.Extension.Length);
                desInfo = new FileInfo(name + "_" + index + desInfo.Extension);
            }
            if (desInfo.Exists)
            {
                return ConfirmFile(destFileName, ++index);
            }
            return desInfo.FullName;
        }

        private void CopyDirectoryContentRecursive(DirectoryInfo sourceDir, DirectoryInfo destinationDir)
        {
            DirectoryInfo[] directories = sourceDir.GetDirectories();
            foreach (DirectoryInfo directoryInfo in directories)
            {
                DirectoryInfo destinationDir2 = destinationDir.CreateSubdirectory(directoryInfo.Name);
                CopyDirectoryContentRecursive(directoryInfo, destinationDir2);
            }
            FileInfo[] files = sourceDir.GetFiles();
            foreach (FileInfo fileInfo in files)
            {
                fileInfo.CopyTo(Path.Combine(destinationDir.FullName, fileInfo.Name));
            }
        }

        private string GetFileItemClientId(string path, string originalPath)
        {
            if (path.IndexOf(originalPath, StringComparison.Ordinal) == 0)
            {
                path = path.Remove(0, originalPath.Length);
            }
            if (path.IndexOf(Path.DirectorySeparatorChar) == 0)
            {
                path = path.Remove(0, 1);
            }
            return path;
        }

        private FileSysItem createFileSysItem(FileInfo file, bool ctThumbnail = false)
        {
            return new FileSysItem()
            {
                name = file.Name,
                size = file.Length,
                isDirectory = false,
                hasSubDirectories = false,
                extension = file.Extension,
                dateCreated = file.CreationTime,
                dateModified = file.LastWriteTime,
                poster = getPoster(file.FullName),
                contentType = file.Extension.ContentType(),
                thumbnail = getThumbnail(file.FullName, ctThumbnail),
                key = GetFileItemClientId(file.FullName, baseDir.FullName),
                shareLink = createShareLink(file.FullName),
            };
        }

        private FileSysItem createFileSysItem(DirectoryInfo dir)
        {
            return new FileSysItem()
            {
                size = 0,
                extension = "",
                thumbnail = "",
                name = dir.Name,
                contentType = "",
                isDirectory = true,
                dateCreated = dir.CreationTime,
                dateModified = dir.LastWriteTime,
                key = GetFileItemClientId(dir.FullName, baseDir.FullName),
                hasSubDirectories = dir.GetDirectories().Count() > 0,
                shareLink = createShareLink(dir.FullName),
            };
        }
        private string getPoster(string path)
        {
            var item = new FileInfo(path);
            if (item.Exists)
            {
                var imageFile = new FileInfo(item.FullName.Replace(item.Extension, ".jpg"));
                if (imageFile.Exists)
                {
                    return createShareLink(imageFile.FullName);
                }
            }
            return string.Empty;
        }

        private string getThumbnail(string path, bool ctThumbnail = false)
        {
            var item = new FileInfo(path);
            if (item.Exists)
            {
                var thumbnail = item.FullName + ".thumbnail";
                if (!File.Exists(thumbnail) && ctThumbnail)
                {
                    createThumbnail(path);
                }
                if (File.Exists(thumbnail))
                {
                    return File.ReadAllText(thumbnail);
                }
            }
            return "";
        }

        private void createThumbnail(string path)
        {
            var item = new FileInfo(path);
            if (item.Extension.ContentType().StartsWith("image"))
            {
                try
                {
                    var source = Image.Load(item.FullName);
                    if (source != null)
                    {
                        var rate = 80.0 / source.Width;
                        if (source.Height > source.Width)
                        {
                            rate = 80.0 / source.Height;
                        }
                        source.Mutate((s) => s.Resize((int)(source.Width * rate), (int)(source.Height * rate)));
                        File.WriteAllText(item.FullName + ".thumbnail", source.ToBase64String(JpegFormat.Instance));
                    }
                }
                catch { }
            }
            else if (ffmpegEnabled && item.Extension.ContentType().StartsWith("video"))
            {
                createVideoThumbnail(item);
            }
        }

        private void splitVideoToHsl(FileInfo file)
        {
            var mp4File = conertVedioToMp4(file);
            if (mp4File.Exists)
            {
                createVideoThumbnail(mp4File);
                var dir = Directory.CreateDirectory(mp4File.FullName + ".slices");
                if (dir.Exists && !string.IsNullOrWhiteSpace(mp4File.DirectoryName))
                {
                    var listFilePath = Path.Combine(dir.FullName, $"{mp4File.Name}.smp4");
                    var args = $"-i \"{mp4File.FullName}\" -vcodec copy -acodec copy -hls_time 5 -hls_list_size 0 -f hls \"{listFilePath}\"";
                    if (executeffmpegCmd(args) && File.Exists(listFilePath))
                    {
                        var newLines = new List<string>();
                        var lines = File.ReadAllLines(listFilePath);
                        foreach (var line in lines)
                        {
                            if (!string.IsNullOrWhiteSpace(line) && line.EndsWith(".ts"))
                            {
                                newLines.Add(createShareLink(Path.Combine(dir.FullName, line)));
                            }
                            else
                            {
                                newLines.Add(line);
                            }
                        }
                        File.WriteAllLines(Path.Combine(mp4File.DirectoryName, $"{mp4File.Name}.smp4"), newLines);
                        var thumbnailFile = new FileInfo(mp4File.FullName + ".thumbnail");
                        if (thumbnailFile.Exists)
                        {
                            thumbnailFile.CopyTo(mp4File.FullName + ".smp4.thumbnail", true);
                        }
                    }
                }
            }
        }

        private string createShareLink(string path)
        {
            var basePath = baseDir.Parent?.FullName.Length + 1 ?? 0;
            return $"/orginone/kernel/load/{_targetFlag}{path.Substring(basePath).Random32(10)}";
        }

        private FileInfo conertVedioToMp4(FileInfo file)
        {
            if (file.Extension.ContentType() != "video/mp4")
            {
                var newFile = new FileInfo(file.FullName.Replace(file.Extension, ".mp4"));
                var args = $"-i \"{file.FullName}\" \"{newFile.FullName}\"";
                executeffmpegCmd(args);
                return newFile;
            }
            return file;
        }

        private void createVideoThumbnail(FileInfo file)
        {
            var imageFile = new FileInfo(file.FullName.Replace(file.Extension, ".jpg"));
            if (!imageFile.Exists)
            {
                var args = $"-i \"{file.FullName}\" -y -f image2 -ss 1 -t 0.001 \"{imageFile.FullName}\"";
                if (executeffmpegCmd(args))
                {
                    createThumbnail(imageFile.FullName);
                    var thumFile = new FileInfo(imageFile.FullName + ".thumbnail");
                    if (thumFile.Exists)
                    {
                        thumFile.CopyTo(file.FullName + ".thumbnail", true);
                    }
                }
            }
        }

        private bool executeffmpegCmd(string args)
        {
            if (!ffmpegEnabled) return false;
            try
            {
                var info = new ProcessStartInfo
                {
                    UseShellExecute = false,
                    CreateNoWindow = true,
                    FileName = new FileInfo(_ffmpegPath).FullName,
                    Arguments = args,
                };
                var pro = Process.Start(info);
                if (pro != default)
                {
                    pro.WaitForExit();
                    return true;
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
            return false;
        }
    }
}
